---
title: Navigation Menu
description: Shows a collection of navigation links.
---

{/* prettier-ignore-start */}
{/* prettier-ignore-end */}

import Code from '@/components/Code.astro';
import { LinkButton } from '@/components/react/LinkButton';
import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import importedCode from '@rnr/reusables/components/ui/navigation-menu?raw';

<LinkButton href='https://rn-primitives.vercel.app/navigation-menu'>Navigation Menu Primitive</LinkButton>
<LinkButton target='_blank' href='https://rnr-showcase.vercel.app/navigation-menu'>
  Demo
</LinkButton>

<br />

Shows a collection of navigation links.

### Installation

<Tabs>
  <TabItem label='CLI'> 
    ```bash
    npx @react-native-reusables/cli@latest add navigation-menu
    ```
  </TabItem>
  <TabItem label='Manual'>
    <Aside type='tip' title='Dependencies'>
      Before copy/pasting, add the <a href='https://rn-primitives.vercel.app/navigation-menu' className='text-white font-bold'> navigation-menu primitive</a> to your project.
    </Aside>

    <br />

    **Copy/paste the following code to `~/components/ui/navigation-menu.tsx`:**

    <Code code={importedCode} lang='tsx' title='~/components/ui/navigation-menu.tsx' />
  </TabItem>
</Tabs>


### Usage

<Aside type="caution">

  Requires a `<PortalHost />` as the last child of your `<Root/>` (`app/_layout.tsx`) component

  ```tsx
  import { PortalHost } from '@rn-primitives/portal';

  function Root() {
    return (
      <>
        <Stack />
        {/* Default Portal Host (one per app) */}
        <PortalHost />
      </>
    );
  }
  ```

</Aside>

```tsx
import type { TextRef } from '@rn-primitives/types';
import { useNavigation } from 'expo-router';
import * as React from 'react';
import { Platform, Pressable, StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from '~/components/ui/navigation-menu';
import { Text } from '~/components/ui/text';
  import { Sparkles } from '~/lib/icons/Sparkles';
import { cn } from '~/lib/utils';

const components: { title: string; href: string; description: string }[] = [
  {
    title: 'Alert Dialog',
    href: '/alert-dialog/alert-dialog-universal',
    description:
      'A modal dialog that interrupts the user with important content and expects a response.',
  },
  ...
];

function Example() {
  const insets = useSafeAreaInsets();
  const contentInsets = {
    top: insets.top,
    bottom: insets.bottom,
    left: 12,
    right: 12,
  };
  const [value, setValue] = React.useState<string>();
  const navigation = useNavigation();

  function closeAll() {
    setValue('');
  }

  React.useEffect(() => {
    const sub = navigation.addListener('blur', () => {
      closeAll();
    });

    return sub;
  }, []);

  return (
    <>
      {Platform.OS !== 'web' && !!value && (
        <Pressable
          onPress={() => {
            setValue('');
          }}
          style={StyleSheet.absoluteFill}
        />
      )}
      <NavigationMenu value={value} onValueChange={setValue}>
        <NavigationMenuList>
          <NavigationMenuItem value='getting-started'>
            <NavigationMenuTrigger>
              <Text>Getting started</Text>
            </NavigationMenuTrigger>
            <NavigationMenuContent insets={contentInsets}>
              <View
                role='list'
                className='web:grid gap-3 p-6 md:w-[400px] lg:w-[500px] web:lg:grid-cols-[.75fr_1fr]'
              >
                <View role='listitem' className='web:row-span-3'>
                  <NavigationMenuLink asChild>
                    <View className='flex web:select-none flex-col justify-end rounded-md web:bg-gradient-to-b web:from-muted/50 web:to-muted native:border native:border-border p-6 web:no-underline web:outline-none web:focus:shadow-md web:focus:shadow-foreground/5'>
                      <Sparkles size={16} className='text-foreground' />
                      <Text className='mb-2 mt-4 text-lg native:text-2xl font-medium'>
                        react-native-reusables
                      </Text>
                      <Text className='text-sm native:text-base leading-tight text-muted-foreground'>
                        Universal components that you can copy and paste into your apps. Accessible.
                        Customizable. Open Source.
                      </Text>
                    </View>
                  </NavigationMenuLink>
                </View>
                <ListItem href='/docs' title='Introduction'>
                  <Text>
                    Re-usable components built using Radix UI on the web and Tailwind CSS.
                  </Text>
                </ListItem>
                <ListItem href='/docs/installation' title='Installation'>
                  <Text>How to install dependencies and structure your app.</Text>
                </ListItem>
                <ListItem href='/docshttps://rn-primitives.vercel.app/typography' title='Typography'>
                  <Text>Styles for headings, paragraphs, lists...etc</Text>
                </ListItem>
              </View>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem value='components'>
            <NavigationMenuTrigger>
              <Text className='text-foreground'>Components</Text>
            </NavigationMenuTrigger>
            <NavigationMenuContent insets={contentInsets}>
              <View
                role='list'
                className='web:grid w-[400px] gap-3 p-4 md:w-[500px] web:md:grid-cols-2 lg:w-[600px] '
              >
                {components.map((component) => (
                  <ListItem key={component.title} title={component.title} href={component.href}>
                    {component.description}
                  </ListItem>
                ))}
              </View>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem value='documentation'>
            <NavigationMenuLink onPress={closeAll} className={navigationMenuTriggerStyle()}>
              <Text>Documentation</Text>
            </NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </>
  );
}

const ListItem = React.forwardRef<
  TextRef,
  React.ComponentPropsWithoutRef<typeof Text> & { title: string; href: string }
>(({ className, title, children, ...props }, ref) => {
  // TODO: add navigationn to `href` on `NavigationMenuLink` onPress
  return (
    <View role='listitem'>
      <NavigationMenuLink
        ref={ref}
        className={cn(
          'web:block web:select-none gap-1 rounded-md p-3 leading-none no-underline text-foreground web:outline-none web:transition-colors web:hover:bg-accent active:bg-accent web:hover:text-accent-foreground web:focus:bg-accent web:focus:text-accent-foreground',
          className
        )}
        {...props}
      >
        <Text className='text-sm native:text-base font-medium text-foreground leading-none'>
          {title}
        </Text>
        <Text className='line-clamp-2 text-sm native:text-base leading-snug text-muted-foreground'>
          {children}
        </Text>
      </NavigationMenuLink>
    </View>
  );
});
ListItem.displayName = 'ListItem';
```

## Props

### NavigationMenuRoot

Extends [`View`](https://reactnative.dev/docs/view#props) props

|       Prop        |            Type            |         Note          |
| :---------------: | :------------------------: | :-------------------: |
|      value\*      |          boolean           |                       |
|  onValueChange\*  |  (value: boolean) => void  |                       |
|      asChild      |          boolean           |     _(optional)_      |
|   delayDuration   |           number           | Web only _(optional)_ |
| skipDelayDuration |           number           | Web only _(optional)_ |
|        dir        |       'ltr' \| 'rtl'       | Web only _(optional)_ |
|    orientation    | 'horizontal' \| 'vertical' | Web only _(optional)_ |

### NavigationMenuList

Extends [`View`](https://reactnative.dev/docs/view#props) props

|  Prop   |  Type   |     Note     |
| :-----: | :-----: | :----------: |
| asChild | boolean | _(optional)_ |

### NavigationMenuItem

Extends [`Pressable`](https://reactnative.dev/docs/pressable#props) props

|  Prop   |  Type   |     Note     |
| :-----: | :-----: | :----------: |
| value\* | string  |              |
| asChild | boolean | _(optional)_ |

### NavigationMenuTrigger

Extends [`Pressable`](https://reactnative.dev/docs/pressable#props) props

|  Prop   |  Type   |     Note     |
| :-----: | :-----: | :----------: |
| asChild | boolean | _(optional)_ |

### NavigationMenuContent

Extends [`View`](https://reactnative.dev/docs/view#props) props

|          Prop           |                     Type                     |           Note           |
| :---------------------: | :------------------------------------------: | :----------------------: |
|         asChild         |                   boolean                    |       _(optional)_       |
|       forceMount        |              true \| undefined               |       _(optional)_       |
|       alignOffset       |                    number                    | Native Only _(optional)_ |
|         insets          |                    Insets                    | Native Only _(optional)_ |
|     avoidCollisions     |                   boolean                    | Native Only _(optional)_ |
|          align          |         'start' \| 'center' \| 'end'         | Native Only _(optional)_ |
|          side           |              'top' \| 'bottom'               | Native Only _(optional)_ |
|       sideOffset        |                    number                    | Native Only _(optional)_ |
| disablePositioningStyle |                   boolean                    | Native Only _(optional)_ |
|          loop           |                   boolean                    |  Web Only _(optional)_   |
|     onEscapeKeyDown     |        (event: KeyboardEvent) => void        |  Web Only _(optional)_   |
|  onPointerDownOutside   |  (event\: PointerDownOutsideEvent) => void   |  Web Only _(optional)_   |
|     onFocusOutside      |      (event: FocusOutsideEvent) => void      |  Web Only _(optional)_   |
|    onInteractOutside    | PointerDownOutsideEvent \| FocusOutsideEvent |  Web Only _(optional)_   |

### NavigationMenuLink

Extends [`Pressable`](https://reactnative.dev/docs/pressable#props) props

|  Prop   |  Type   |     Note     |
| :-----: | :-----: | :----------: |
| active  | boolean | _(optional)_ |
| asChild | boolean | _(optional)_ |

### NavigationMenuViewport

_Should only be used for web_

Extends [`View`](https://reactnative.dev/docs/view#props) props except `children`

### NavigationMenuIndicator

Extends [`View`](https://reactnative.dev/docs/view#props) props